import threading
import collections
from utils.logger import get_logger

logger = get_logger("data_cache.py+DataCache")


class DataCache:
    """
    Thread-safe data cache for PLC-MQTT communication.
    
    This cache implements thread-safe operations for storing and retrieving data
    between PLC client (producer) and MQTT publisher (consumer).
    
    Features:
    - Thread-safe operations with locks
    - Configurable cache size
    - Blocking operations when cache is full or empty
    """
    
    def __init__(self, max_size=100):
        """
        Initialize the data cache.
        
        :param max_size: Maximum number of items to store in the cache
        """
        self.max_size = max_size
        self._data_queue = collections.deque(maxlen=max_size)
        self._lock = threading.RLock()  # Reentrant lock
        self._not_empty = threading.Condition(self._lock)  # Condition for waiting when empty
        self._not_full = threading.Condition(self._lock)   # Condition for waiting when full
        
    def put(self, data):
        """
        Put data into the cache. Blocks if the cache is full.
        
        :param data: Data to store in the cache
        """
        with self._not_full:
            # Wait until there's space in the cache
            while len(self._data_queue) >= self.max_size:
                logger.debug("Cache is full, waiting for space...")
                self._not_full.wait()
                
            # Add data to the queue
            self._data_queue.append(data)
            logger.debug(f"Data added to cache, current size: {len(self._data_queue)}")
            
            # Notify waiting consumers that data is available
            self._not_empty.notify()
            
    def get_latest(self):
        """
        Get the latest data from the cache and clear the cache.
        
        :return: Latest data item or None if cache is empty
        """
        with self._lock:
            if len(self._data_queue) == 0:
                return None
                
            # Get the latest item (most recent)
            latest_data = self._data_queue[-1]
            
            # Clear the cache
            self._data_queue.clear()
            
            # Notify waiting producers that space is available
            self._not_full.notify_all()
            
            logger.debug("Retrieved latest data from cache and cleared cache")
            return latest_data
            
    def get_all_sequential(self):
        """
        Get all data from the cache in sequential order and clear the cache.
        
        :return: List of all data items in order or empty list if cache is empty
        """
        with self._lock:
            if len(self._data_queue) == 0:
                return []
                
            # Get all items in order
            all_data = list(self._data_queue)
            
            # Clear the cache
            self._data_queue.clear()
            
            # Notify waiting producers that space is available
            self._not_full.notify_all()
            
            logger.debug(f"Retrieved {len(all_data)} items from cache and cleared cache")
            return all_data
            
    def size(self):
        """
        Get current size of the cache.
        
        :return: Number of items in the cache
        """
        with self._lock:
            return len(self._data_queue)
            
    def is_empty(self):
        """
        Check if cache is empty.
        
        :return: True if cache is empty, False otherwise
        """
        with self._lock:
            return len(self._data_queue) == 0
            
    def is_full(self):
        """
        Check if cache is full.
        
        :return: True if cache is full, False otherwise
        """
        with self._lock:
            return len(self._data_queue) >= self.max_size